package com.github.mrs.spring;

import java.beans.Introspector;
import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.*;

/** 模拟模拟AnnotationConfigApplicationContext
 * date: 2023/9/19 18:52
 * author: MR.孙
 */
public class MrsSpringApplicationContext {


    private Class configClass;

    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();

    private Map<String, Object> singletonObjects = new HashMap<>();

    private List<BeanPostProcess> beanPostProcessList = new ArrayList<>();


    public MrsSpringApplicationContext(Class configClass) {

        //1.赋值配置类成员变量
        this.configClass = configClass;

        //spring启动时会完成扫描，并判断是不是单例的
        // 并将标注@Compnent的bean信息和带有@Scope的bean信息放到BeanDefinition中 得到Scope的bean信息放到BeanDefinition中
        // beanDefinitionMap这个map就包含了beanName，和bean相关的信息
        scan(configClass);

        //单例池什么时候放的实例？
        //在spring启动时，进行扫描，然后判断哪些是单例的bean，然后再扫描之后放入单例池即可
        //获取所有的bean定义属性相关信息
        preInstantiateSingletons();//实例化单例->单例池
    }

    private void preInstantiateSingletons() {
        Set<Map.Entry<String, BeanDefinition>> entries = beanDefinitionMap.entrySet();
        for (Map.Entry<String, BeanDefinition> entry : entries) {
            String beanName = entry.getKey();
            BeanDefinition beanDefinition = entry.getValue();
            //如果是单例，则放入单例池
            if (beanDefinition.getScope().equals("singleton")) {
                Object bean = createBean(beanName, beanDefinition);
                singletonObjects.put(beanName, bean);
            }
        }
    }

    /**
     * 根据Class创建一个实例
     * @param beanName
     * @param beanDefinition
     * @return
     */
    public Object createBean(String beanName, BeanDefinition beanDefinition) {
        Class clazz = beanDefinition.getType();
        Object instance = null;
        try {
            instance = clazz.newInstance();

            //对加了@Autowired的属性进行依赖注入
            Field[] fields = clazz.getDeclaredFields();
            //判断是否加了@Autowired注解
            for (Field field : fields) {
                //加了@Autowired注解
                if (field.isAnnotationPresent(Autowired.class)) {
                    String name = field.getName();//orderService
                    //根据属性名获取bean对象
                    //在getBean()方法中根据beanName判断bean的信息是否存在，并判断是单例还是多例并返回
                    Object bean = getBean(name);
                    //访问私有变量
                    field.setAccessible(true);
                    field.set(instance, bean);
                }
            }

            //依赖注入之后Aware回调。如果Bean实例实现了BeanNameAware接口，就调用重写的setter方法，给Bean实例的beanName变量赋值
            if (instance instanceof BeanNameAware) {
                //调用方法给容器名赋值
                ((BeanNameAware)instance).setBeanName(beanName);
            }


            //初始化之前...
            //循环遍历BeanPostProcess，执行初始化之前的方法
            for (BeanPostProcess beanPostProcess : beanPostProcessList) {
                instance = beanPostProcess.postProcessBeforeInitialization(instance, beanName);
            }


            //初始化
            //如果这个实例实现的InitializingBean这个接口 我们就对这个bean进行初始化操作
            if (instance instanceof InitializingBean) {
                ((InitializingBean)instance).afterPropertiesSet();
            }

            //Aop
            //Aop是基于BeanPostProcess实现的
            //初始化之后
            // 遍历后置处理器列表，执行后置处理器的before方法，在Bean 的初始化方法（如 @PostConstruct 注解的方法）被调用之前被自动调用
            for (BeanPostProcess beanPostProcess : beanPostProcessList) {
                instance = beanPostProcess.postProcessAfterInitialization(instance, beanName);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return instance;
    }


    private void scan(Class configClass) {
        // 2.解析配置类
        // 通过反射，判断配置类字节码有没有注解@ComponentScan
        // 如果有，就根据@ComponentScan传入的扫描路径，扫描那个包下的Bean
        ComponentScan annotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
        //获取包扫描路径 com.github.mrs.test
        String path = annotation.value();

        //根据包扫描路径扫描所有的带有@Component的类文件
        //需要注意的是com.github.mrs.test是有两个位置的，是项目的位置，还是target.classes中的类文件呢
        //需要target.classes中的类文件

        //获取容器类对象的类加载器
        ClassLoader classLoader = MrsSpringApplicationContext.class.getClassLoader();
        //com.github.mrs.test 替换为com/github/mrs/test
        path = path.replace(".", "/");
        // 类加载器获取扫描包的绝对路径 ->就是个文件夹
        URL resource = classLoader.getResource(path);//  file:/F:/BuildProject/IdeaProject/mrs-spring/target/classes/com/github/mrs/test

        //找到这个类下的所有类，然后筛选出带@Component注解的类文件
        //String file = resource.getFile();// /F:/BuildProject/IdeaProject/mrs-spring/target/classes/com/github/mrs/test
        // 把URL绝对路径对象封装成File对象 ->文件夹
        File file = new File(resource.getFile());
        //如果是文件夹
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                //判断每个文件是否标注@Component注解，是基于类文件还是类对象，答案是类对象Class，而现在是文件
                String fileName = f.getAbsolutePath();
                if (fileName.endsWith(".class")) {
                    //将文件名转为类名；D:\xxx\com\vince\service\AppConfig.class ----> AppConfig
                    //这里其实不应该写死成“com”，只是方便起见，其实应该用更复杂的逻辑截取全名为类名
                    String className = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class"));
                    // System.out.println(className); com\github\mrs\test\AppConfig
                    className= className.replace("\\", ".");
                    // System.out.println(className);
                    try {
                        Class clazz = classLoader.loadClass(className);
                        //判断Class对象上是否标注@Component注解
                        if (clazz.isAnnotationPresent(Component.class)) {

                            //如果@Component("类名")有设置类名，Bean名就是设置的这个类名
                            Component component = (Component) clazz.getAnnotation(Component.class);
                            String beanName = component.value();
                            //BeanPostProcess是否是参数的超类或者clazz是否为BeanPostProcess的实现类
                            if (BeanPostProcess.class.isAssignableFrom(clazz)) {

                                BeanPostProcess o = (BeanPostProcess)clazz.newInstance();
                                beanPostProcessList.add(o);
                            }


                            //b.如果@Component没有设置类名，就将首字母小写后的Bean类名设为Bean名
                            if ("".equals(beanName)) {
                                beanName = Introspector.decapitalize(clazz.getSimpleName());//工具类将字符串校验后首字母小写
                            }

                            BeanDefinition beanDefinition = new BeanDefinition();
                            beanDefinition.setType(clazz);

                            //有@Component注解，这时候还要考虑@Scope为prototype的情况
                            if (clazz.isAnnotationPresent(Scope.class)) {
                                //其它情况
                                Scope scope = (Scope) clazz.getAnnotation(Scope.class);
                                beanDefinition.setScope(scope.value());
                            } else {
                                //默认，单例情况
                                beanDefinition.setScope("singleton");
                            }


                            beanDefinitionMap.put(beanName, beanDefinition);
                        }

                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }


            }
        }
    }

    /**
     * 容器里拥有getBean()方法，获取Bean
     * @param beanName
     * @return
     */
    public Object getBean(String beanName) {
        //从BeanDefinitionMap->BeanDefinition对象

       if (beanDefinitionMap.containsKey(beanName)) {
           BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
           //拿到bean的一些定义属性进行判断，scope是singleton还是prototype

           if (beanDefinition.getScope().equals("singleton")) {
               //单例 如何实现？如何保证同一个beanName获取的是同一个bean 单例池【Map】
               Object o = singletonObjects.get(beanName);
               if (o == null) {
                  //如果单例池查到的是null，则新创建Bean，再给单例池赋值
                   o = createBean(beanName, beanDefinition);
                   singletonObjects.put(beanName, o);
               }
               return o;
           } else {
               //多例 每次创建不同的实例
               Object bean = createBean(beanName, beanDefinition);
               return bean;

           }
       } else {
           //没有这样的bean
           throw new NullPointerException();
       }
    }

}
